package spc.webos.endpoint;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.log4j.Logger;

import spc.webos.cache.ICache;
import spc.webos.cache.Map2Cache;
import spc.webos.data.IMessage;
import spc.webos.queue.AccessTPool;
import spc.webos.queue.IOnMessage;
import spc.webos.queue.IQueueAccess;
import spc.webos.queue.QueueMessage;
import spc.webos.queue.ibmmq.MQAccessTPool;
import spc.webos.queue.ibmmq.MQCnnPool;
import spc.webos.queue.ibmmq.QueueAccess;
import spc.webos.util.StringX;

public class ESB2 implements Endpoint {
	protected IQueueAccess synAccess;
	protected boolean shortCnn;
	protected IQueueAccess access;
	protected String reqQName = "REQ";

	protected String repQName = "REP";

	protected String jvm = "";
	private static boolean init;
	protected int timeout = 60;
	protected Map mqmd;
	protected String synReplyToQ;
	protected boolean matchMsgId = true;
	protected List<AccessTPool> synResponsePools;
	protected ICache cache = new Map2Cache(90L);
	protected IOnMessage synResponseOnMessage = new ESB2OnMessage(this);
	protected String asynReplyToQ;
	protected List<AccessTPool> asynResponsePools;
	protected IOnMessage asynResponseOnMessage;
	protected List<AccessTPool> requestPools;
	protected IOnMessage requestOnMessage;
	public Logger log = Logger.getLogger(super.getClass());
	protected static ESB2 esb2 = new ESB2();
	private String instanceId;
	private String startTm;
	static long LAST_TIME;

	public synchronized void init() throws Exception {
		if (init)
			throw new Exception("init repeat!!!");
		init = true;

		this.startTm = new SimpleDateFormat("yyyyMMddHHmmssSSS")
				.format(new Date());
		this.instanceId = getSeqNb();
		this.cache.setName("C_" + this.instanceId);

		String messageId = this.jvm + "00000000000000000000000000000000";
		messageId = messageId.substring(0, 24);
		this.log.info("Ver:8.0.5_20150525, cache:"
				+ this.cache.getName()
				+ ", startTm:"
				+ this.startTm
				+ "\nstart syn response pools:"
				+ ((this.synResponsePools == null) ? 0 : this.synResponsePools
						.size()) + ", synReplyToQ:" + this.synReplyToQ
				+ ", msgId:" + messageId + ",matchMsgId:" + this.matchMsgId);
		for (int i = 0; (this.synResponsePools != null)
				&& (i < this.synResponsePools.size()); ++i) {
			AccessTPool pool = (AccessTPool) this.synResponsePools.get(i);
			if (StringX.nullity(pool.getName()))
				pool.setName(this.synReplyToQ + "::" + i);
			if (this.matchMsgId)
				pool.setMessageId(messageId);
			if (StringX.nullity(pool.getQname()))
				pool.setQname(this.synReplyToQ);
			pool.setOnMessage(this.synResponseOnMessage);
			pool.init();
			pool.startAll();
		}

		this.log.info("start asyn response pools:"
				+ ((this.asynResponsePools == null) ? 0
						: this.asynResponsePools.size()) + ", asynReplyToQ:"
				+ this.asynReplyToQ);
		for (int i = 0; (this.asynResponsePools != null)
				&& (i < this.asynResponsePools.size());) {
			AccessTPool pool = (AccessTPool) this.asynResponsePools.get(i);
			if (StringX.nullity(pool.getQname()))
				pool.setQname(this.asynReplyToQ);
			if (StringX.nullity(pool.getName()))
				pool.setName(pool.getQname() + "::" + i);
			pool.setOnMessage(this.asynResponseOnMessage);
			pool.init();
			pool.startAll();

			++i;
		}

		this.log.info("start request pools:"
				+ ((this.requestPools == null) ? 0 : this.requestPools.size()));
		for (int i = 0; (this.requestPools != null)
				&& (i < this.requestPools.size()); ++i) {
			AccessTPool pool = (AccessTPool) this.requestPools.get(i);
			if (StringX.nullity(pool.getName()))
				pool.setName(pool.getQname() + "::" + i);
			pool.setOnMessage(this.requestOnMessage);
			pool.init();
			pool.startAll();
		}

		this.log.info("ESB2 init over...\n\n");
	}

	public synchronized void destroy() {
		destory();
	}

	public void setLocation(String location) throws Exception {
		throw new RuntimeException("No method!!!");
	}

	public String getSeqNb() {
		return getSeqNb(15);
	}

	public String getSeqNb(int len) {
		String seqNb = this.jvm + getTimeSN(8) + random(24);
		return seqNb.substring(0, len);
	}

	public String getSeqNb(String appCd) {
		String seqNb = this.jvm + appCd + getTimeSN(8) + random(24);
		return seqNb.substring(0, 24);
	}

	public String getSeqNb(String appCd, int len) {
		String seqNb = this.jvm + appCd + getTimeSN(8) + random(24);
		return seqNb.substring(0, len);
	}

	public synchronized String getTimeSN(int num) {
		long time = System.currentTimeMillis();
		if (time <= LAST_TIME)
			time = LAST_TIME + 1L;
		LAST_TIME = time;
		String str = String.valueOf(time);
		return (num > str.length()) ? str : str.substring(str.length() - num);
	}

	public String random(int len) {
		String random = String.valueOf(Math.random()).substring(2)
				+ String.valueOf(Math.random()).substring(2);
		random = random.replaceAll("E", "").replaceAll("-", "");
		return (len > random.length()) ? random : random.substring(random
				.length() - len);
	}

	public void sendResponse(byte[] buf) throws Exception {
		if (this.log.isDebugEnabled())
			this.log.debug("snd response qname:" + this.repQName + ", buf:"
					+ new String(buf, "UTF-8"));
		this.access.send(this.repQName, new QueueMessage(buf));
		if (this.log.isDebugEnabled())
			this.log.debug("sucess to snd response qname:" + this.repQName
					+ ", buf:" + new String(buf, "UTF-8"));
	}

	public void send(String qname, QueueMessage qmsg) throws Exception {
		this.access.send(qname, qmsg);
	}

	public void execute(Executable exe) throws Exception {
		if (this.log.isDebugEnabled())
			this.log.debug("request:" + new String(exe.request, "UTF-8"));
		if ((this.synResponsePools != null)
				&& (this.synResponsePools.size() > 0))
			asyn(exe);
		else
			syn(exe);
	}

	public void asyn(Executable exe) throws Exception {
		if (exe.messageId == null)
			exe.messageId = exe.correlationID;
		String corId = new String(exe.correlationID);
		int timeout = (exe.timeout > 0) ? exe.timeout : this.timeout;
		String reqQName = (StringX.nullity(exe.reqQName)) ? this.reqQName
				: exe.reqQName;
		String repQName = (StringX.nullity(exe.repQName)) ? this.synReplyToQ
				: exe.repQName;
		if (this.log.isInfoEnabled())
			this.log.info("asyn ver:8.0.2_20150521, reqQ:" + reqQName
					+ ", repQ:" + repQName + ", jvm:" + this.jvm + ", timeout:"
					+ timeout + ", return:" + (!exe.withoutReturn) + ", corId:"
					+ corId + ", matchMsgId:" + this.matchMsgId + ", cache:"
					+ this.cache.getName() + ",size:" + this.cache.size());
		exe.reqTime = System.currentTimeMillis();
		QueueMessage reqqmsg = new QueueMessage(exe.request, exe.correlationID,
				exe.messageId, timeout, reqQName);
		reqqmsg.replyToQ = repQName;
		send(reqQName, reqqmsg);
		if (exe.withoutReturn)
			return;
		QueueMessage qmsg = poll(corId, exe.timeout);
		exe.response = qmsg.buf;
		exe.resTime = System.currentTimeMillis();
		if (this.log.isInfoEnabled())
			this.log.info("cost:" + (exe.resTime - exe.reqTime) + ",len:"
					+ exe.response.length + "\n\n\n");
	}

	public QueueMessage poll(String corId, int timeout) throws Exception {
		return (QueueMessage) this.cache.poll(corId, timeout * 1000);
	}

	public void syn(Executable exe) throws Exception {
		String reqQNm = (StringX.nullity(exe.reqQName)) ? this.reqQName
				: exe.reqQName;
		String repQNm = exe.repQName;
		byte[] corId = exe.correlationID;
		int timeout = (exe.timeout > 0) ? exe.timeout : this.timeout;
		if (this.log.isInfoEnabled())
			this.log.info("syn ver:8.0.2_20150521, shortCnn:" + this.shortCnn
					+ ", reqQ:" + reqQNm + ", repQ:" + repQNm + ", timeout:"
					+ timeout + ", return:" + (!exe.withoutReturn) + ", corId:"
					+ ((corId == null) ? "" : new String(corId)));
		try {
			this.synAccess.send(reqQNm, new QueueMessage(exe.request, corId,
					exe.messageId, timeout, reqQNm));
			if (exe.withoutReturn)
				return;
		} finally {
			QueueMessage qmsg;
			if ((this.shortCnn) && (this.synAccess != null))
				this.synAccess.destroy();
		}
		if ((this.shortCnn) && (this.synAccess != null))
			this.synAccess.destroy();
	}

	public QueueMessage createQueueMessage() {
		QueueMessage qmsg = apply(null);
		qmsg.correlationId = getSeqNb(24).getBytes();
		return qmsg;
	}

	public QueueMessage apply(QueueMessage qmsg) {
		if (qmsg == null)
			qmsg = new QueueMessage();
		String msg = "";
		if ((StringX.nullity(qmsg.replyToQ))
				&& (!StringX.nullity(this.synReplyToQ))) {
			qmsg.replyToQ = this.synReplyToQ;
			msg = msg + "replyToQ:" + this.synReplyToQ + ", ";
		}
		if (qmsg.expirySeconds == 0) {
			qmsg.expirySeconds = this.timeout;
			msg = msg + "expiry:" + this.timeout + ", ";
		}
		if (StringX.nullity(qmsg.putAppName)) {
			qmsg.putAppName = "ESB2_8.0.2";
		}
		if ((this.mqmd == null) || (this.mqmd.size() <= 0))
			return qmsg;
		String val = (String) this.mqmd.get("appIdData");
		if ((StringX.nullity(qmsg.applicationIdData))
				&& (!StringX.nullity(val))) {
			qmsg.applicationIdData = val;
			msg = msg + "appId:" + val + ", ";
		}
		val = (String) this.mqmd.get("appOriginData");
		if ((StringX.nullity(qmsg.applicationOriginData))
				&& (!StringX.nullity(val))) {
			qmsg.applicationOriginData = val;
			msg = msg + "appOrigData:" + val + ", ";
		}
		val = (String) this.mqmd.get("replyToQMgr");
		if ((StringX.nullity(qmsg.replyToQMgr)) && (!StringX.nullity(val))) {
			qmsg.replyToQMgr = val;
			msg = msg + "replyToQMgr:" + val + ", ";
		}

		val = (String) this.mqmd.get("putAppName");
		if ((StringX.nullity(qmsg.putAppName)) && (!StringX.nullity(val))) {
			qmsg.putAppName = val;
			msg = msg + "putAppName:" + val + ", ";
		}
		if (this.log.isInfoEnabled())
			this.log.info("default MQMQ:" + msg);
		return qmsg;
	}

	public void req2rep(IMessage msg) {
		msg.setRefMsgCd(msg.getMsgCd());
		msg.setRefSndNode((StringX.nullity(msg.getSndNode())) ? null : msg
				.getSndNode());
		msg.setRefSndApp(msg.getSndApp());
		msg.setRefSndDt(msg.getSndDt());
		msg.setRefSeqNb(msg.getSeqNb());
		String dt = new SimpleDateFormat("yyyyMMddHHmmssSSS")
				.format(new Date());
		msg.setSeqNb(getSeqNb(15));
		msg.setSndDt(dt.substring(0, 8));
		msg.setSndTm(dt.substring(8, 17));
		msg.setRcvAppCd(null);
	}

	public void initESB() throws Exception {
		Properties p = new Properties();
		p.load(super.getClass().getClassLoader().getResource("esb.properties")
				.openStream());
		init(p);
	}

	public void init(String fileName) throws Exception {
		Properties p = new Properties();
		p.load(super.getClass().getClassLoader().getResource(fileName)
				.openStream());
		init(p);
	}

	public void init(Map map) throws Exception {
		if ((map == null) || (map.size() == 0)) {
			return;
		}
		String val = (String) map.get("access");
		if (!StringX.nullity(val)) {
			setAccess(val);
		}
		val = (String) map.get("synAccess");
		if (!StringX.nullity(val)) {
			setSynAccess(val);
		}
		val = (String) map.get("synResponsePools");
		if (!StringX.nullity(val)) {
			setSynResponsePools(val);
		}
		val = (String) map.get("asynResponsePools");
		if (!StringX.nullity(val)) {
			setAsynResponsePools(val);
		}
		val = (String) map.get("requestPools");
		if (!StringX.nullity(val)) {
			setRequestPools(val);
		}
		val = (String) map.get("synReplyToQ");
		if (!StringX.nullity(val)) {
			this.synReplyToQ = val.trim();
		}
		val = (String) map.get("asynReplyToQ");
		if (!StringX.nullity(val)) {
			this.asynReplyToQ = val.trim();
		}
		val = (String) map.get("matchMsgId");
		if (!StringX.nullity(val)) {
			setMatchMsgId(new Boolean(val.trim()).booleanValue());
		}
		val = (String) map.get("shortCnn");
		if (!StringX.nullity(val)) {
			setShortCnn(new Boolean(val.trim()).booleanValue());
		}
		val = (String) map.get("jvm");
		if (!StringX.nullity(val)) {
			setJvm(val.trim());
		}
		val = (String) map.get("reqQName");
		if (!StringX.nullity(val)) {
			setReqQName(val.trim());
		}
		val = (String) map.get("repQName");
		if (!StringX.nullity(val)) {
			setRepQName(val.trim());
		}
		val = (String) map.get("timeout");
		if (!StringX.nullity(val)) {
			setTimeout(Integer.parseInt(val.trim()));
		}

		Map mqmd = new HashMap();
		Iterator keys = map.keySet().iterator();
		while (keys.hasNext()) {
			String key = keys.next().toString();
			val = map.get(key).toString().trim();
			if (!key.startsWith("MQMD."))
				continue;
			if (StringX.nullity(val))
				continue;
			mqmd.put(key.substring(5), val);
		}
		if (mqmd.size() > 0)
			this.mqmd = mqmd;
		init();
	}

	public synchronized void destory() {
		this.log.warn("ESB2 will destroy...");
		if (this.synAccess != null) {
			this.synAccess.destroy();
			this.synAccess = null;
		}
		if (this.access != null) {
			this.access.destroy();
			this.access = null;
		}

		this.log.info("stop synResponsePools:"
				+ ((this.synResponsePools == null) ? 0 : this.synResponsePools
						.size()));
		for (int i = 0; (this.synResponsePools != null)
				&& (i < this.synResponsePools.size()); ++i)
			((AccessTPool) this.synResponsePools.get(i)).asynStopAll();
		this.synResponsePools = null;

		this.log.info("stop asynResponsePools:"
				+ ((this.asynResponsePools == null) ? 0
						: this.asynResponsePools.size()));
		for (int i = 0; (this.asynResponsePools != null)
				&& (i < this.asynResponsePools.size());) {
			((AccessTPool) this.asynResponsePools.get(i)).asynStopAll();

			++i;
		}
		this.asynResponsePools = null;

		this.cache.removeAll();

		this.log.info("stop requestPools:"
				+ ((this.requestPools == null) ? 0 : this.requestPools.size()));
		for (int i = 0; (this.requestPools != null)
				&& (i < this.requestPools.size()); ++i)
			((AccessTPool) this.requestPools.get(i)).asynStopAll();
		this.requestPools = null;

		init = false;
	}

	public Endpoint clone() throws CloneNotSupportedException {
		throw new CloneNotSupportedException();
	}

	public IQueueAccess getAccess() {
		return this.access;
	}

	public void setAccess(IQueueAccess access) {
		this.access = access;
	}

	public String getReqQName() {
		return this.reqQName;
	}

	public void setReqQName(String reqQName) {
		this.reqQName = reqQName;
	}

	public void setShortCnn(boolean shortCnn) {
		this.shortCnn = shortCnn;
	}

	public String getSynReplyToQ() {
		return this.synReplyToQ;
	}

	public void setSynReplyToQ(String synReplyToQ) {
		this.synReplyToQ = synReplyToQ;
	}

	public String getRepQName() {
		return this.repQName;
	}

	public void setRepQName(String repQName) {
		this.repQName = repQName;
	}

	public List<AccessTPool> getSynResponsePools() {
		return this.synResponsePools;
	}

	public void setSynResponsePools(List<AccessTPool> synResponsePools) {
		this.synResponsePools = synResponsePools;
	}

	public IOnMessage getSynResponseOnMessage() {
		return this.synResponseOnMessage;
	}

	public void setSynResponseOnMessage(IOnMessage synResponseOnMessage) {
		this.synResponseOnMessage = synResponseOnMessage;
	}

	public void setSynResponsePools(String synResponsePools) {
		this.synResponsePools = MQAccessTPool
				.createATPoolList(synResponsePools);
	}

	public ICache getCache() {
		return this.cache;
	}

	public void setCache(ICache cache) {
		this.cache = cache;
	}

	public void setAccess(String chl) throws Exception {
		this.access = new QueueAccess(MQCnnPool.createChlList(chl));
		((QueueAccess) this.access).getMultiChannel().setAlgorithm(1);
	}

	public void setSynAccess(String chl) throws Exception {
		this.synAccess = new QueueAccess(MQCnnPool.createChlList(chl));
	}

	public List<AccessTPool> getRequestPools() {
		return this.requestPools;
	}

	public void setRequestPools(List<AccessTPool> requestPools) {
		this.requestPools = requestPools;
	}

	public void setRequestPools(String requestPools) {
		this.requestPools = MQAccessTPool.createATPoolList(requestPools);
	}

	public IOnMessage getRequestOnMessage() {
		return this.requestOnMessage;
	}

	public void setRequestOnMessage(IOnMessage requestOnMessage) {
		if (requestOnMessage instanceof AbstractBizOnMessage)
			((AbstractBizOnMessage) requestOnMessage).setEsb2(this);
		this.requestOnMessage = requestOnMessage;
	}

	public void setAsynResponsePools(List<AccessTPool> asynResponsePools) {
		this.asynResponsePools = asynResponsePools;
	}

	public void setAsynResponsePools(String asynResponsePools) {
		this.asynResponsePools = MQAccessTPool
				.createATPoolList(asynResponsePools);
	}

	public void setAsynResponseOnMessage(IOnMessage asynResponseOnMessage) {
		this.asynResponseOnMessage = asynResponseOnMessage;
	}

	public String getJvm() {
		return this.jvm;
	}

	public void setJvm(String jvm) {
		this.jvm = jvm;
	}

	public int getTimeout() {
		return this.timeout;
	}

	public void setTimeout(int timeout) {
		this.timeout = timeout;
	}

	public boolean isMatchMsgId() {
		return this.matchMsgId;
	}

	public void setMatchMsgId(boolean matchMsgId) {
		this.matchMsgId = matchMsgId;
	}

	public static ESB2 getInstance() {
		return esb2;
	}

	public String getInstanceId() {
		return this.instanceId;
	}

	public String getStartTm() {
		return this.startTm;
	}
}
